A computer programmer generally creates software programs using a high-level programming language, such as C++, JAVA, VISUAL BASIC, or the like. The computer programmer may define his/her own functions within the computer program using the high-level programming language or may rely on libraries of functions that are already defined and provided to the computer programmer via the programming environment. For example, a computer programmer may develop his/her own functions to create a user interface or rely on functions specified in libraries of functions (which are usually referred to as “libraries”) provided by MICROSOFT, APPLE or other operating system developers. These libraries are often large and the computer programmer generally only uses a small subset of the functions made available by these libraries when coding any one program. In this sense, the libraries may be large but sparsely utilized on a per-program basis.
Once the computer programmer has finished writing a computer program using the high-level programming language, the computer programmer invokes a computer program, referred to as a compiler, to reduce the high-level code to lower-level code, outputting the lower-level code in the form of a destination file. This lower-level code is often referred to as “intermediate representation” or “IR” code. The compiler may perform some forms of optimization on the IR code to reduce the size of the IR code. For example, the compiler may determine that one or more functions of the original high-level code will never be executed during operation (such as debugging functions) and removes portions of the IR code to remove these functions. The compiler then translates the IR code into compiled code, which may be IR code written in a different IR programming language or machine or assembly language code executable by a computing device. The compiler may then output the compiled code along with link data defining how to link this compiled code with the one or more libraries referenced and used by the high-level code.
A program referred to as a “linker” receives the compiled code and the data defining how to link this compiled code with the libraries. The linker then copies the libraries into the correct location relative to the compiled code in accordance with the link data provided by the compiler. In essence, this linker may represent a symbol-based copier, as the libraries and compiled code are nothing more than abstract symbols to the linker, in that it does not perform any form of syntax, semantic or other checks, optimization or the like. The linker copies these libraries in their entirety into the compiled code to form destination data. The libraries include compiled code defining the various functions so that these do not need to be compiled by a compiler again. In this way, the linker may arrange disparate data from multiple sources, e.g., libraries and user-defined compiled code, into a destination data, which may eventually be output as a destination file. The destination file may comprise an executable file in some instances.
As noted above, these libraries are large and may typically include tens, hundreds, if not thousands of functions. Moreover, the user-defined code may only sparsely utilize these functions in that this code only utilizes a small subset or percentage of the functions provided by the library. Considering that libraries of this size may require significant amounts of memory when copied into the destination file, the linker may invoke a computer program referred to as a “link time optimizer” to perform dead code elimination so as to reduce the amount of memory consumed by the resulting libraries and user-defined code. The link time optimizer may copy the libraries over to the executable file and then evaluate the destination file to eliminate any dead code, i.e., code that would not be reachable during execution. However, this linker-level dead code elimination often takes a large amount of time and involves significant processing power that consumes a substantial amount of energy, as a result of having to evaluate potentially thousands of functions to determine whether these functions are invoked or, in other words, used. As a result of these inefficiencies, the linker-level dead code elimination is often limited in its applicability especially when this form of linker-level dead code elimination is required to be implemented in real-time or near real-time or by power-sensitive devices, such as mobile phones, mobile media players, mobile gaming devices and the like.